.NET provides component services via call
interception. To intercept a call, .NET must insert a proxy between the
client and the object and do some pre- and post-call processing. Call
interception is the key to valuable, productivity-oriented component
services. For example, interception can provide thread safety by trying
to acquire a lock before accessing the object and then proceeding to
call the object. While the call is in progress, calls coming in from
other clients are intercepted as well, and those calls will be blocked
when they try to access the lock. When the call returns from the object
to the proxy, it unlocks the lock to allow other clients to use the
object. Another example of an interception-based component service is call authorization:
the proxy can verify that the caller has appropriate credentials (such
as being a member of a specified role) to call the object, and deny
access if it does not. The problem with call interception is that an app
domain is too coarse an execution scope; even though cross-app domain
calls always go through a proxy, some app domain calls use direct
references. To address this problem, app domains are further subdivided
into contexts, and objects execute in contexts rather than app domains
(see Figure 1).
In .NET, the context
is the innermost execution scope of an object. Components indicate to
.NET which services they require using special context attributes. All
objects in the same context are compatible in terms of their component
services requirements, and .NET doesn't need to perform pre- and
post-call processing when these objects access one another. You can,
therefore, define a context as a logical grouping of objects that rely
on the same set of services. Calls into the context are intercepted to
ensure that the objects always get the appropriate runtime environment
they require to operate.
1. Contexts and Object Types
What if a component
doesn't require any services? Why should it pay the interception penalty
for cross-context access? To address this point, .NET objects are
classified into two categories: those that care about component services
and want to use them, and those that don't. By default, objects are
context-agnostic and have no context affinity, which means they always
execute in the contexts of their calling clients. Because such
context-agnostic objects "jump" from one context to the next, they are
referred to as context-agile objects.
The clients of a context-agile object each have a direct reference to
it, and no proxies are involved when making intra-app domain calls. Note
that objects that derive from MarshalByRefObject
are accessed via a proxy across app domains but are agile inside an app
domain. Marshal-by-value objects are also context-agile.
The other type of object is called a context-bound object.
Context-bound objects always execute in the same context. The
designation and affinity of a context-bound object to a context is
decided when the object is created and is fixed for the life of the
object. To qualify as a context-bound object, the object must derive
directly (or have one of its base classes derive) from the abstract
class ContextBoundObject:
public class MyClass : ContextBoundObject
{...}
The client of a
context-bound object never has a direct reference to it. Instead, the
client always interacts with a context-bound object via a proxy. Because
ContextBoundObject is derived from MarshalByRefObject,
every context-bound object is also marshaled by reference across app
domain boundaries. This makes perfect sense, because the context-bound
object can't leave its context, let alone its app domain.
2. Component Services Types
.NET provides two kinds of component services: context-bound services and Enterprise Services. The context-bound services are available to any context-bound object. .NET offers only one such service: the synchronization domain. Using the Synchronization context attribute, a context-bound component gains automatic synchronization and lock sharing:
[Synchronization]
public class MyClass : ContextBoundObject
{
public MyClass( )
{}
public void DoSomething( )
{}
}
When you add the Synchronization
attribute, .NET ensures that only one thread at a time is allowed to
access the object, without requiring you to spend the effort
implementing this functionality yourself, and you don't have to worry
about deadlocks. Clients of such objects don't need to worry about
synchronization either. Although this is the only context-bound service
provided out-of-the-box, the .NET context is an extensible mechanism,
and you can define your own custom context attributes
for custom services and extensions.
The second kind of component services are .NET Enterprise Services (see the sidebar ".NET Enterprise Services").
Enterprise Services offer more than 20 component services covering an
impressive array of domains, from transactions to loosely coupled events
to web services. Enterprise Services are available only to classes
derived from the class ServicedComponent. ServicedComponent is in turn derived from ContextBoundObject,
so you can say that Enterprise Services are a specialization of
context-bound services. If you develop a serviced component, you
normally indicate which services the component relies on by using
dedicated attributes.
Enterprise Services attributes aren't .NET context attributes, but rather COM+ context attributes. |
|
.NET Enterprise Services are
a set of component services designed to ease the development of
Enterprise applications. .NET Enterprise Services are the result of
integrating COM+ into .NET, and they offer the same range of services
essential to any Enterprise application. For the most part, the
semantics of the services, the algorithms behind them, the benefits, and
the implied programming models remain exactly the same as they were for
COM components. .NET components that take advantage of Enterprise
Services are called serviced components because they derive from the class ServicedComponent, defined in the System.EnterpriseServices
namespace. You can configure the services by using context attributes
in your code. For example, if you want .NET to maintain a pool of your
objects, and you want there to be at least 3 but no more than 10 objects
in the pool, use the ObjectPooling attribute:
using System.EnterpriseServices; [ObjectPooling(MinPoolSize = 3,MaxPoolSize = 10)] public class MyComponent : ServicedComponent {...}
If you also want transaction support, use the Transaction attribute:
using System.EnterpriseServices; [Transaction] [ObjectPooling(MinPoolSize = 3,MaxPoolSize = 10)] public class MyComponent : ServicedComponent {...}
You can also
configure the pool parameters (and any other services) after deployment
by using the COM+ Component Services Explorer.
However, due to .NET's ability to persist the attributes in the
assembly metadata, there is often no need to use the Component Services
Explorer for services configuration.
|